#include "PowderGame.h"
#include <math.h>
#include <new>

Particle::Particle(ParticleDescriptor* type) : descriptor(type) {}
void Particle::applyForce(const vec2f& force) { forceAccumulator+=force; }
void Particle::applyImpulse(const vec2f& impulse) { impulseAccumulator+=impulse; }
void Particle::applyImpulseImmediate(const vec2f& impulse){
	velocity+=impulse/descriptor->density;
}
void Particle::addVelocity(const vec2f& v) { velocity+=v; }
void Particle::setVelocity(const vec2f& v) { velocity=v; }
void Particle::simulate(float seconds) {
	velocity+=(impulseAccumulator+forceAccumulator*seconds)/descriptor->density;
	impulseAccumulator=forceAccumulator=vec2f(0.0f,0.0f);
}
const vec2f& Particle::getVelocity() const { return velocity; }
vec2f Particle::getMomentum() const { return velocity*descriptor->density; }

Cell::Cell() : pressure(1.0f), p_pressure(1.0f), particle(NULL),
		nArray(new Cell*[8]), pressureForceAccumulator(0),
		pressureImpulseAccumulator(0), div(0.0f){
	for(size_t i=0; i<8; ++i)
		nArray[i]=NULL;
}
Cell::~Cell() { delete [] nArray; }
void Cell::swapParticles(Cell& other){
	std::swap(particle, other.particle);
}
void Cell::interact(){
	if(wind.x || wind.y){
	#ifndef CELL_SIMPLE_METHOD
		for(size_t i=0; i<8; i+=1){
			if(nArray[i]==NULL)
				continue;
			vec2f relevantWind=wind.dot(UNIT[i])*UNIT[i]*0.125;
			nArray[i]->applyImpulse(relevantWind);
			applyImpulse(-relevantWind);
		}
	#else
		for(size_t i=0; i<8; i+=2){
			if(nArray[i]){
				switch(i){
				case 0: //right
				case 4: //left
					nArray[i]->applyImpulse(vec2f(wind.x/8,0));
					applyImpulse(vec2f(nArray[i]->wind.x/8,0));
					break;
				case 2: //up
				case 6: //down
					nArray[i]->applyImpulse(vec2f(0,wind.y/8));
					applyImpulse(vec2f(0,nArray[i]->wind.y/8));
					break;
				}
			}
		}
	#endif
	}
}
void Cell::diffuse(float diff){
	float inPressure=0;
	for(size_t i=0; i<8; i+=2){
		if(nArray[i]){
			inPressure+=nArray[i]->pressure;
		}
	}
	pressure = (p_pressure+diff*inPressure)/(1+4*diff);
}
bool Cell::simulate(float seconds){
	p_pressure=pressure;
	p_wind=wind;
	wind+=windForeAccumulator*seconds + windImpulseAccumulator;
	windForeAccumulator=windImpulseAccumulator=vec2f(0.0f, 0.0f);
	pressure+=pressureForceAccumulator*seconds + pressureImpulseAccumulator;
	pressureForceAccumulator=pressureImpulseAccumulator=0;
	if(pressure<0)
		pressure=0;
	if(particle){
		particle->applyForce(wind+vec2f(0.0f, -1000.0f));
		particle->simulate(seconds);
		return std::max(particle->getVelocity().x, particle->getVelocity().y)*seconds>=1.0f;
	}
	return false;
}
void Cell::applyForce(const vec2f& force){
	windForeAccumulator+=force;
}
void Cell::applyImpulse(const vec2f& impulse){
	windImpulseAccumulator+=impulse;
}
void Cell::forcePressure(float rate){
	pressureForceAccumulator+=rate*10;
}
void Cell::impulsePressure(float rate){
	pressureImpulseAccumulator+=rate;
}
void Cell::addWind(const vec2f& w){
	wind+=w;
}
void Cell::setWind(const vec2f& w){
	wind=w;
}
void Cell::addPressure(float p){
	pressure+=p;
}
void Cell::setPressure(float p){
	pressure=p;
}
vec2f Cell::thisPositionEndsUpHere(float seconds){
	return wind*-seconds;
}
void Cell::project(){
	//pressure = (divergence + surrounding pressures )/4
	float ppsum=0;
	vec2f vchange;
	for(size_t i=0; i<8; i+=2){
		if(nArray[i]){
			ppsum+=nArray[i]->p_pressure;
			switch(i){
			case 0: //right
				vchange.x+=nArray[i]->p_pressure;
				break;
			case 4: //left
				vchange.x-=nArray[i]->p_pressure;
				break;
			case 2: //up
				vchange.y+=nArray[i]->p_pressure;
				break;
			case 6: //down
				vchange.y-=nArray[i]->p_pressure;
				break;
			}
		}
		else ppsum+=1.0f;
	}
	pressure=(div+ppsum)/4;
	applyImpulse(vchange*0.5f);
	//update velocity baced on pressure
	//TODO this
}
void Cell::updateDiv(){
	div = 0;
	for(size_t i=0; i<8; i+=2){
		if(nArray[i]){
			switch(i){
			case 0: //right
				div-=nArray[i]->wind.x;
				break;
			case 4: //left
				div+=nArray[i]->wind.x;
				break;
			case 2: //up
				div-=nArray[i]->wind.y;
				break;
			case 6: //down
				div+=nArray[i]->wind.y;
				break;
			}
		}
	}
}

GameManager::GameManager(IO_Manager* iomgr) : field(NULL), myContext(new GameContext(this)),
		lastStateUpdate(0), myManager(iomgr), magnitude(15),
		defaultDescriptor(1.0f, true, this), maxMag(1.0f), maxVel(1.0f) {
	myManager->registerGameContext(*myContext);
	pointsInCircle(magnitude, circle);
}
GameManager::~GameManager(){
	delete [] field;
	delete myContext;
}
void GameManager::addMagnitude(int i){
	if(i<0 && magnitude>1){
		--magnitude;
	}
	else if(i>0 && magnitude<10){
		++magnitude;
	}
	pointsInCircle(magnitude, circle);
}
void GameManager::processLclick(const vec2i& cartesianPosition){
	for(size_t i=0; i<circle.size(); ++i){
		vec2i coord = cartesianPosition+circle[i];
		if(coord.x<0 || coord.x>=fieldSize.x || coord.y<=0 || coord.y>=fieldSize.y)
			continue;
		Cell& cell = accessField(coord);
		cell.applyImpulse(vec2f(0.0f,-50.0f));
		cell.setPressure(circle[i].magnitude());
		//if(!cell.particle)
		//	cell.particle=new Particle(&defaultDescriptor);
	}
}
Cell& GameManager::accessField(vec2i coords){
	return field[coords.y*fieldSize.x+coords.x];
}
const Cell& GameManager::accessField(vec2i coords) const{
	return field[coords.y*fieldSize.x+coords.x];
}
void GameManager::interactCells(){
	int mSize=fieldSize.x*fieldSize.y;
	for(int i=0; i<mSize; ++i){
		field[i].interact();
	}
}
void GameManager::diffuseCells(float diff){
	int mSize=fieldSize.x*fieldSize.y;
	for(int i=0; i<mSize; ++i){
		field[i].diffuse(diff);
	}
}
void GameManager::simulateCells(float seconds){
	int mSize=fieldSize.x*fieldSize.y;
	for(int i=0; i<mSize; ++i){
		field[i].simulate(seconds);
	}
}
void GameManager::advectCells(float seconds){
	Cell* cp=field;
	vec2i cCoord;
	for(cCoord.y=0; cCoord.y<fieldSize.y; ++cCoord.y)
		for(cCoord.x=0; cCoord.x<fieldSize.x; ++cCoord.x){
			cp->p_pressure=interpPressure(cCoord, cp->thisPositionEndsUpHere(seconds));
			++cp;
		}
}
void GameManager::projectCells(){
	int mSize=fieldSize.x*fieldSize.y;
	for(int i=0; i<mSize; ++i){
		field[i].project();
	}
}
void GameManager::resizeGame(vec2i newSize){ //breaks on second resize
	size_t newBufferSize=newSize.x*newSize.y;
	/*temporary fix, game resets when resized*/
	delete [] field;
	field = NULL;
	Cell* newField = new (std::nothrow) Cell[newBufferSize];
	if(field){ //if the old field isn't empty
		int minY=std::min(fieldSize.y, newSize.y);
		int minX=std::min(fieldSize.x, newSize.x);
		for(size_t y=0; y<minY; ++y){
			for(size_t x=0; x<minX; ++x){
				newField[y*newSize.x+x]=field[y*fieldSize.x+x];
			}
		}
		delete [] field;
	}
	field=newField;
	fieldSize=newSize;
	updateNeighbours();
}
float GameManager::interpPressure(vec2i coords, vec2f tpeuh){
	coords+=tpeuh;
	tpeuh-=vec2i(tpeuh);
	vec2i addCoords(tpeuh+vec2f(0.5f, 0.5f));
	if(coords.x < 2 || coords.y < 2 || coords.x > fieldSize.x-3 || coords.y > fieldSize.y-3)
		return 0;
	return (1-tpeuh.y)*(accessField(coords).pressure*(1-tpeuh.x) + accessField(coords+vec2i(addCoords.x,0)).pressure*(tpeuh.x))
			+(tpeuh.y)*(accessField(coords+vec2i(0, addCoords.y)).pressure*(1-tpeuh.x)+accessField(coords+addCoords).pressure*(tpeuh.x));
}
void GameManager::updateNeighbours(){
	//only called on init or resize
	vec2i cCoord;
	for(cCoord.y=0; cCoord.y<fieldSize.y; ++cCoord.y)
		for(cCoord.x=0; cCoord.x<fieldSize.x; ++cCoord.x)
			cellNeighbours(cCoord, accessField(cCoord).nArray);
}
void GameManager::cellNeighbours(vec2i cellCoords, Cell** nArray){
	for(size_t i=0; i<8; ++i){
		vec2i coord = cellCoords+OCTANT_MAP[i];
		if(coord.x>=0 && coord.y>=0 && coord.x < fieldSize.x && coord.y <fieldSize.y)
			nArray[i]=&accessField(coord);
		else
			nArray[i]=NULL;
	}
}
void GameManager::updateState(){
	if(lastStateUpdate==0){
		lastStateUpdate=SDL_GetTicks();
		return;
	}
	Uint32 now = SDL_GetTicks();
	if(now-lastStateUpdate < 50)
		return;
	float seconds=(now-lastStateUpdate)/1000.0f;
	diffuseCells(seconds*50.0f); //new pressure is up to date, previous is old
	simulateCells(seconds); //both are synchronized, divergence updated
	advectCells(seconds*50.0f); //previous is now newer than just pressure, ok to fook whit pressuer
	//divergence still similar, use it to project
	projectCells();
	lastStateUpdate=now;
}
void GameManager::updateScreen(std::stringstream& out) const{
	myManager->cartesianCoordinates();
	vec2i cCoord;
	glClear(GL_COLOR_BUFFER_BIT);
	//float seconds = 1000.0f/(SDL_GetTicks()-lastStateUpdate);
	glBegin(GL_POINTS);
	float cMax=0.0f;
	float vMax=0.0f;
	Cell* cp=field;
	for(cCoord.y=0; cCoord.y<fieldSize.y; ++cCoord.y)
		for(cCoord.x=0; cCoord.x<fieldSize.x; ++cCoord.x){
			glColor4ub(0xff, 0xff, 0xff, 0xff);
			float mag=cp->pressure;
			float vel=cp->wind.magnitude();
			cMax=std::max(mag,cMax);
			vMax=std::max(vel,vMax);
			glColor4f(0.0f, 0.5f*mag/maxMag, 0.5f*vel/maxVel, 1.0f);
			glVertex2i(cCoord.x, cCoord.y);
			if(cp->particle){
				glColor4f(1.0f, 1.0f, 1.0f, .5f);
				glVertex2i(cCoord.x,cCoord.y);
			}
			++cp;
		}
	maxMag=cMax;
	maxVel=vMax;
	glEnd();
	out << "Max Pressure: " << cMax;
	myManager->screenCoordinates();
}

GameContext::GameContext(GameManager* mgr) : myManager(mgr) {}
void GameContext::updateState(){
	myManager->updateState();
}
void GameContext::renderToScreen(std::stringstream& out){
	myManager->updateScreen(out);
}
void GameContext::resize(const vec2i& s){
	myManager->resizeGame(s);
}
void GameContext::processMouse(const MouseInfo& mouseinfo){
	if(mouseinfo.left)
		myManager->processLclick(mouseinfo.mouseCartesianPosition);
}























